﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


namespace Compiler
{
    public class LR1Builder
    {
        private Grammar grammar;

        private Graph<HashSet<LR1Item>, Symbol> graph;
        private HashSet<LR1Item> StartSet;
        private LR1Item EndItem;
        private Builder.BuildMethod buildMethod;

        public LR1Builder(Grammar grammar, Builder.BuildMethod buildMethod)
        {
            if (buildMethod != Builder.BuildMethod.LR1 && buildMethod != Builder.BuildMethod.LALR)
                throw new ArgumentException();

            this.grammar = grammar;
            this.buildMethod = buildMethod;
        }
        
        public void BuildStatemachine()
        {
            //todo: der grammatik eine künstliche startregel hinzufügen
            Production prod = grammar.Productions[grammar.Start].First();
            
            graph = new Graph<HashSet<LR1Item>, Symbol>(
                        new SetEqualityComparer<LR1Item, LR1Item.LR1ItemEqualityComparer>(), 
                        new Symbol.SymbolEqualityComparer(),
                        new Func<HashSet<LR1Item>, string>(Tools.HashSetPrinter<LR1Item>));

            HashSet<Symbol> eofset = new HashSet<Symbol>();
            eofset.Add(Symbol.EOF);

            EndItem = new LR1Item(LR0Item.CreateItem_DotAtEnd(prod), Symbol.EOF);

            var lr1Cmp = new LR1Item.LR1ItemEqualityComparer();
            StartSet = new HashSet<LR1Item>(lr1Cmp);
            StartSet.Add( new LR1Item(LR0Item.CreateItem_DotAtStart(prod), Symbol.EOF) );
            StartSet = LR1Item.Closure1(grammar, StartSet);

            Stack<HashSet<LR1Item>> OpenStack = new Stack<HashSet<LR1Item>>();
            
            OpenStack.Push(StartSet);

            while (OpenStack.Count > 0)
            {
                HashSet<LR1Item> working = OpenStack.Pop();

                if (graph.TryAddState(working))
                {
                    HashSet<Symbol> doneSet = new HashSet<Symbol>(new Symbol.SymbolEqualityComparer());
                    foreach (LR1Item item in working)
                    {
                        Symbol symbolOnRightSide = item.GetSymbolOnRightSide();
                        if (symbolOnRightSide != null && !doneSet.Contains(symbolOnRightSide))
                        {
                            doneSet.Add(symbolOnRightSide);
                            HashSet<LR1Item> newState = LR1Item.Goto1(grammar, working, symbolOnRightSide);
                            graph.AddRelation(working, symbolOnRightSide, newState);
                            OpenStack.Push(newState);
                        }
                    }
                }
            }

            //Console.WriteLine(graph.ToString());

            if (buildMethod == Builder.BuildMethod.LALR)
            {
                graph = graph.Merge(new LR1Item.KernelSetComparer(), LR1Item.ItemMerger);
                //Console.WriteLine("\n\n" + graph.ToString());
            }

            //graph.CheckReferences();
        }


       

        public ParseTable BuildTable()
        {
            if (graph == null || StartSet == null || EndItem == null)
                throw new Exception("Calculate Statemaschine first");

            ParseTable pt = new ParseTable();
            var lr1Cmp = new LR1Item.LR1ItemEqualityComparer();

            var stateMapper = graph.CreateStateMapper();
            pt.Start = stateMapper[StartSet];

            foreach (Pair<HashSet<LR1Item>, Symbol> pair in graph.GetPairEnumerator())
            {
                if (pair.Item2 is Terminal)
                    pt.AddShift(stateMapper[pair.Item1], pair.Item2, stateMapper[graph.FollowState(pair)]);
                else if (pair.Item2 is Nonterminal)
                    pt.AddJumpRule(stateMapper[pair.Item1], pair.Item2, stateMapper[graph.FollowState(pair)]);
            }

            foreach (var state in graph.GetStateEnumerator())
            {
                if (state.Contains(EndItem))
                    pt.AddAccept(stateMapper[state], Symbol.EOF);
            }

            var Terminals = grammar.getTerminals();
            foreach (var set in graph.GetStateEnumerator())
                foreach (var item in set.Where(i => i.IsItemWithDotFarRight() && !lr1Cmp.Equals(i, EndItem)))
                {
                    foreach (var terminal in Terminals.Where(term => item.LookaheadIs(term)))
                        pt.AddReduce(stateMapper[set], terminal, item.getItemProduction());

                    if (item.LookaheadIs(Symbol.EOF))
                        pt.AddReduce(stateMapper[set], Symbol.EOF, item.getItemProduction());
                }

            return pt;
        }


        //dot -Tpng hurr.gv -o state2.png
        public string VisualGraph()
        {
            StringBuilder b = new StringBuilder();
            var stateMapper = graph.CreateStateMapper();
            var g = graph.graph;
            b.Append(@"
digraph g {
graph [fontsize=30 labelloc=""t"" label="""" splines=true overlap=false rankdir = ""LR""];
ratio = auto;");

            foreach (var state in g.Keys)
            {
                b.Append(string.Format(@"
""state{0}"" [ style = ""filled"" penwidth = 1 fillcolor = ""white"" fontname = ""Courier New"" shape = ""Mrecord"" 
  label = 
  <<table border=""0"" cellborder=""0"" cellpadding=""3"" bgcolor=""white"">
  <tr><td bgcolor=""black"" align=""center"" colspan=""2""><font color=""white"">State #{0}</font></td></tr>
", stateMapper[state].ToString()));

                foreach (var lritem in state)
                {
                    b.Append(string.Format(@"<tr><td align=""left"" port=""r1"">{0}</td><td bgcolor=""grey"" align=""right"">{1}</td></tr>",
                        lritem.ToStringLR0Item().Replace("=>", "-&gt;"),lritem.ToStringLookaheadSet().Replace("{","").Replace("}","")));
                    b.Append("\n");
                }

                b.Append(@"</table>>];");
              
                b.Append("\n\n");
            }


            foreach (var state in g.Keys)
            {
                
                foreach (var input in g[state].Keys)
                {
                    b.Append("\n");
                    b.Append(string.Format("state{0} -> state{1}", stateMapper[state], stateMapper[g[state][input]]));
                    
                    b.Append(string.Format(@" [ penwidth = 5 fontsize = 28 fontcolor = ""black"" label = ""{0}"" ];", input));
                }
            }

            b.Append("}");

            return b.ToString();
        }
    }
}
